MoonSharp


概要

UnityでLuaが動かせるので、使ってみていた。

MoonSharp

http://www.moonsharp.org


使い方次第では綺麗に自分の足が消し飛んだりすると思う。

MODライクな使い方は考えていなくて、特にデバッグや自動テストのためのツールとして使おうと思っていた。


結果的には2501というプロジェクトでとても役になった。



ざっくりとできることまとめ

・luaをUnity上で実行できる

・実行するluaはstringからいける

・特定のlua実行コンテキストを持ち回すことができる

・実行コンテキストのluaテーブルを好きに書き換えることができる(!)

・インスタンスを渡すこともできるので、好きにメソッドが呼べる

・luaのコルーチンも使える



やりにくいことまとめ

・当然だけど複雑な引数を持つ関数は実行しづらい



逆引きっぽいコンテンツ

以下は、勉強しながら触ってたらいい感じに逆引きみたいになった学習ログ。



staticな実行コンテキストでluaを実行する

luaの中で定義した関数をluaの中で実行し、結果をC#側で得ることができる。


double MoonSharpFactorial()

{

string script = @"    

-- defines a factorial function

function fact (n)

if (n == 0) then

return 1

else

return n*fact(n - 1)

end

end


return fact(5)";


DynValue res = Script.RunString(script);

return res.Number;

}



Scriptの実行コンテキストを保持する

実行コンテキストはstaticではなく生成できる。


double MoonSharpFactorial()

{

string scriptCode = @"    

-- defines a factorial function

function fact (n)

if (n == 0) then

return 1

else

return n*fact(n - 1)

end

end


return fact(5)";


Script script = new Script();

DynValue res = script.DoString(scriptCode);

return res.Number;

}



Luaコードのコンテキストに干渉する

変数の値とかに干渉できる。


double MoonSharpFactorial()

{

string scriptCode = @"    

-- defines a factorial function

function fact (n)

if (n == 0) then

return 1

else

return n*fact(n - 1)

end

end


return fact(mynumber)";


Script script = new Script();


script.Globals["mynumber"] = 7;


DynValue res = script.DoString(scriptCode);

return res.Number;

}


global値に干渉できる。

唐突だな~~。



特定のメソッドを呼ぶ

DoStringで評価してから、Call関数で関数名を指定して引数を渡す、ってことができる。


double MoonSharpFactorial2()

{

string scriptCode = @"    

-- defines a factorial function

function fact (n)

if (n == 0) then

return 1

else

return n*fact(n - 1)

end

end";


Script script = new Script();


script.DoString(scriptCode);


DynValue res = script.Call(script.Globals["fact"], 4);


return res.Number;

}



DynValue型は関数ポインタとして取り出せる

別にC#側から自由に呼べるわけではない。

単にポインタとして保持できる、っていうだけ。

double MoonSharpFactorial()

{

string scriptCode = @"    

-- defines a factorial function

function fact (n)

if (n == 0) then

return 1

else

return n*fact(n - 1)

end

end";


Script script = new Script();


script.DoString(scriptCode);


DynValue luaFactFunction = script.Globals.Get("fact");


DynValue res = script.Call(luaFactFunction, 4);


return res.Number;

}



DynValueはTuple型にもなれる

要素が三つのTupleで、indexアクセスができる。


DynValue ret = Script.RunString("return true, 'ciao', 2*3");


// prints "Tuple"

Console.WriteLine("{0}", ret.Type);


// Prints:

//   Boolean = true

//   String = "ciao"

//   Number = 6

for (int i = 0; i < ret.Tuple.Length; i++)

Console.WriteLine("{0} = {1}", ret.Tuple[i].Type, ret.Tuple[i]);



関数を渡せる

luaの中の関数定義を書き換えることで、C#の関数をluaから呼ぶことができる。

かなり無理やり。


private static int Mul(int a, int b)

{

    return a * b;

}


private static double CallbackTest()

{

    string scriptCode = @"    

        -- defines a factorial function

        function fact (n)

            if (n == 0) then

                return 1

            else

                return Mul(n, fact(n - 1));

            end

        end";


    Script script = new Script();


    script.Globals["Mul"] = (Func<int, int, int>)Mul;


    script.DoString(scriptCode);


    DynValue res = script.Call(script.Globals["fact"], 4);


    return res.Number;

}



置き換えなしにC#のメソッドを呼ぶ

特定のAttrを付けたクラスのメソッドを、Luaから呼び出す。

下記の例だと、MyClass クラスのcalcHyptenuseメソッドを、名前ベースでluaのグローバル変数にバインドすることなく呼び出している。


・実際にはUserData.RegisterAssemblyというメソッドでバインドしている。

・クラスのインスタンスはバインドしてる

とかがある。

これstaticにしても呼べるのかな?


[MoonSharpUserData]

class MyClass

{

public double calcHypotenuse(double a, double b)

{

return Math.Sqrt(a * a + b * b);

}

}


double CallMyClass1()

{

string scriptCode = @"    

return obj.calcHypotenuse(3, 4);

";


// Automatically register all MoonSharpUserData types

UserData.RegisterAssembly();


Script script = new Script();


// Pass an instance of MyClass to the script in a global

script.Globals["obj"] = new MyClass();


DynValue res = script.DoString(scriptCode);


return res.Number;

}



Attrつけなくても、クラス個別で受け付ける方法

クラスインスタンスに対して、thisとかも使えるのを確認した。


class MyClass

{

public double CalcHypotenuse(double a, double b)

{

return Math.Sqrt(a * a + b * b);

}

}


static double CallMyClass2()

{

string scriptCode = @"    

return obj.calcHypotenuse(3, 4);

";


// Register just MyClass, explicitely.

UserData.RegisterType<MyClass>();


Script script = new Script();


// create a userdata, again, explicitely.

DynValue obj = UserData.Create(new MyClass());// この部分thisでもいける。

script.Globals.Set("obj", obj);


DynValue res = script.DoString(scriptCode);


return res.Number;

}



luaからstaticなメソッドを呼ぶ

いくつか方法があるが、以下が楽。


double MyClassStaticThroughPlaceholder()

{

string scriptCode = @"    

return obj.calcHypotenuse(3, 4);

";


// Automatically register all MoonSharpUserData types

UserData.RegisterAssembly();


Script script = new Script();


script.Globals["obj"] = typeof(MyClassStatic);// Set関数は使えない。


DynValue res = script.DoString(scriptCode);


return res.Number;

}



使用感

2501では、これらを利用して、lua沼にbackboneというデバッグ用にnewしたインスタンスを沈めていた。

するとluaからbackboneのインスタンスとそのpublicメソッドがぶったたけるので、とても便利、っていう。

メモリ計測、ログ送付、スクリーンショット送付とかをやっている。


さらに、ユーザーのインスタンス(実行中のみ存命なインスタンス)を沈めることで、外部からリアルタイムでのメソッド着火を可能にしている。

これはとても柔軟で、登録されているaliveなインスタンス一覧、またpublicかつluaから渡せるインスタンスで動かせるメソッドを全部実行可能。